feat(agent): cumulative prompt/completion token-split accessors#1
feat(agent): cumulative prompt/completion token-split accessors#1indykish wants to merge 1 commit into
Conversation
…h commit The fork patch is now reviewed (agentsfleet/nullclaw#1) and released as tag v2026.5.29-zmb.1 at the same commit (127b5ac4). Swapped the pin ref from the branch name to the release tag — same commit, so the content hash is byte-identical (verified via zig fetch), a more semantic + immutable reference. Spec §1 + Depends-on updated to cite the release. Both build graphs compile against the tag pin. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…e accessors The agent already normalizes prompt_tokens/completion_tokens per response but folds only total_tokens. Accumulate both splits at the turn and summary-compaction sites and expose promptTokensUsed()/completionTokensUsed() beside tokensUsed(), so embedders can meter prompt-side and completion-side spend separately without re-deriving usage from history. clearSessionState() zeroes both new counters alongside total_tokens (@hasField-guarded, matching the existing pattern), so /new and /reset don't leave by-side metering carrying stale per-session figures while tokensUsed() reads 0. The "/new clears history" test asserts it. Carried on this fork atop v2026.5.29 until upstream exposes split accessors. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
127b5ac to
dd85a21
Compare
| self.total_tokens += normalized_usage.total_tokens; | ||
| self.prompt_tokens_total += normalized_usage.prompt_tokens; | ||
| self.completion_tokens_total += normalized_usage.completion_tokens; |
There was a problem hiding this comment.
Split counters silently zero-out when provider omits per-side data
When a provider sets total_tokens > 0 but leaves prompt_tokens = 0 and completion_tokens = 0 (neither normalization block fires because total_tokens is already non-zero), the accumulators for prompt_tokens_total and completion_tokens_total receive 0 for that turn while total_tokens advances correctly. Over multiple such turns, promptTokensUsed() + completionTokensUsed() falls short of tokensUsed() with no signal to the caller. Since the stated purpose of these accessors is by-side billing, an embedder that relies on them for cost metering will silently under-charge prompt-side and completion-side spend for any provider that only surfaces a total — a hard-to-detect billing gap.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agent/root.zig
Line: 2443-2445
Comment:
**Split counters silently zero-out when provider omits per-side data**
When a provider sets `total_tokens > 0` but leaves `prompt_tokens = 0` and `completion_tokens = 0` (neither normalization block fires because `total_tokens` is already non-zero), the accumulators for `prompt_tokens_total` and `completion_tokens_total` receive 0 for that turn while `total_tokens` advances correctly. Over multiple such turns, `promptTokensUsed() + completionTokensUsed()` falls short of `tokensUsed()` with no signal to the caller. Since the stated purpose of these accessors is by-side billing, an embedder that relies on them for cost metering will silently under-charge prompt-side and completion-side spend for any provider that only surfaces a total — a hard-to-detect billing gap.
How can I resolve this? If you propose a fix, please make it concise.
Summary
Carries one commit atop upstream tag v2026.5.29: the
Agentalready normalizesprompt_tokens/completion_tokensper response but folds onlytotal_tokens. This accumulates both splits at the turn and summary-compaction sites and exposespromptTokensUsed()/completionTokensUsed()beside the existingtokensUsed(), so an embedder can meter prompt-side and completion-side spend separately without re-deriving usage from history.Why a fork
usezombiebills platform-model token spend by side and needs these accessors. We hold no push/release rights on upstreamnullclaw/nullclaw, so the patch rides this fork (usezombie/nullclaw) until upstream exposes equivalent accessors — at which point the consumer pin returns to the upstream tag and this fork is dropped.Changes
prompt_tokens_total/completion_tokens_totalcumulative fields onAgent(default 0).promptTokensUsed()/completionTokensUsed()accessors.Agent tokens trackingtest extended to cover the splits.Verification
zig build test --summary all— the pristine tag and this patched branch fail only the same 5 pre-existing redis-environment integration tests (those diallocalhost:6379, which on a dev box is an unrelated container; clean in upstream CI). No new failures. Released asv2026.5.29-zmb.1.Greptile Summary
Adds cumulative
prompt_tokens_total/completion_tokens_totalfields toAgent, folds them at both the per-turn response and summary-compaction sites, and exposespromptTokensUsed()/completionTokensUsed()alongside the existingtokensUsed()so embedders can meter by token side. TheclearSessionStatereset and/new-command test are also updated.root.zig): twou64fields default to 0, accumulated at the same two fold-sites astotal_tokens, exposed via typed accessors.commands.zig):clearSessionStategains@hasField-guarded zeroing of both new fields, mirroring the existingtotal_tokenspattern; tested by an extended/newassertion inroot.zig.Confidence Score: 4/5
The change is safe to merge for non-billing consumers; the billing-accuracy gap for providers that report total but not per-side tokens should be addressed before production metering is switched on.
The accumulation logic is correct for all providers that supply split data, and the session-reset path is handled cleanly. The one real concern is that neither normalization block back-fills split tokens when a provider gives total_tokens without per-side data — those turns leave prompt_tokens_total and completion_tokens_total at zero while total_tokens advances, so promptTokensUsed() + completionTokensUsed() can silently diverge from tokensUsed(). For the stated purpose of by-side billing metering, that gap is a current defect on the hot path.
src/agent/root.zig — the accumulation sites at lines 2443–2445 and 2864–2865 need a guard or documented caveat for the case where a provider omits per-side token counts.
Important Files Changed
Flowchart
%%{init: {'theme': 'neutral'}}%% flowchart TD A[LLM Response received] --> B[Normalize TokenUsage] B --> C{total==0 and splits present?} C -- Yes --> D[total = prompt + completion] C -- No --> E{total==0 and all zero and text present?} D --> F[Fold into Agent counters] E -- Yes --> G[completion = estimated tokens\ntotal = estimated tokens] E -- No --> F G --> F F --> H[total_tokens accumulates] F --> I[prompt_tokens_total accumulates] F --> J[completion_tokens_total accumulates] H --> K[tokensUsed] I --> L[promptTokensUsed] J --> M[completionTokensUsed] C -- No --> W{total greater than 0 but splits zero?} W -- Yes --> X[splits stay 0 - silent gap] X --> FPrompt To Fix All With AI
Reviews (2): Last reviewed commit: "feat(agent): accumulate prompt/completio..." | Re-trigger Greptile